package com.carey.blog.repository.impl;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.Latkes;
import com.carey.blog.framework.latke.RunsOnEnv;
import com.carey.blog.framework.latke.cache.Cache;
import com.carey.blog.framework.latke.cache.CacheFactory;
import com.carey.blog.framework.latke.repository.RepositoryException;
import com.carey.blog.framework.latke.repository.SortDirection;
import com.carey.blog.framework.latke.repository.gae.AbstractGAERepository;
import com.carey.blog.model.ArchiveDate;
import com.carey.blog.repository.ArchiveDateRepository;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;

/**
 * Archive date Google App Engine repository.
 * 
 */
public final class ArchiveDateGAERepository extends AbstractGAERepository
		implements ArchiveDateRepository {

	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger
			.getLogger(ArchiveDateGAERepository.class.getName());
	/**
	 * Cache.
	 */
	private static final Cache<String, Object> CACHE;

	static {
		final RunsOnEnv runsOnEnv = Latkes.getRunsOnEnv();
		if (!runsOnEnv.equals(RunsOnEnv.GAE)) {
			throw new RuntimeException(
					"GAE repository can only runs on Google App Engine, please "
							+ "check your configuration and make sure "
							+ "Latkes.setRunsOnEnv(RunsOnEnv.GAE) was invoked before "
							+ "using GAE repository.");
		}

		CACHE = CacheFactory.getCache("ArchiveDateGAERepositoryCache");
	}

	@Override
	public String getName() {
		return ArchiveDate.ARCHIVE_DATE;
	}

	@Override
	public JSONObject getByArchiveDate(final String archiveDate)
			throws RepositoryException {
		try {
			final Query query = new Query(getName());
			query.addFilter(ArchiveDate.ARCHIVE_DATE,
					Query.FilterOperator.EQUAL,
					ArchiveDate.DATE_FORMAT.parse(archiveDate));
			final PreparedQuery preparedQuery = getDatastoreService().prepare(
					query);
			final Entity entity = preparedQuery.asSingleEntity();

			if (null == entity) {
				return null;
			}

			final Map<String, Object> properties = entity.getProperties();

			return new JSONObject(properties);
		} catch (final ParseException e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new RepositoryException(e);
		}
	}

	@Override
	public List<JSONObject> getArchiveDates() throws RepositoryException {
		final String cacheKey = "[archiveDates]";
		@SuppressWarnings("unchecked")
		List<JSONObject> ret = (List<JSONObject>) CACHE.get(cacheKey);
		if (null != ret) {
			LOGGER.log(Level.FINEST, "Got the archive dates from cache");
		} else {
			ret = new ArrayList<JSONObject>();
			final com.carey.blog.framework.latke.repository.Query query = new com.carey.blog.framework.latke.repository.Query()
					.addSort(ArchiveDate.ARCHIVE_DATE, SortDirection.DESCENDING);
			final JSONObject result = get(query);

			try {
				final JSONArray archiveDates = result
						.getJSONArray(Keys.RESULTS);

				for (int i = 0; i < archiveDates.length(); i++) {
					final JSONObject archiveDate = archiveDates
							.getJSONObject(i);
					ret.add(archiveDate);
				}
			} catch (final JSONException e) {
				LOGGER.log(Level.SEVERE, e.getMessage(), e);
				throw new RepositoryException(e);
			}

			try {
				removeForUnpublishedArticles(ret);
			} catch (final JSONException e) {
				LOGGER.log(Level.SEVERE, e.getMessage(), e);
			}

			CACHE.put(cacheKey, ret);

			LOGGER.log(Level.FINEST,
					"Got the archive dates, then put it into cache");
		}

		return ret;
	}

	/**
	 * Removes archive dates of unpublished articles from the specified archive
	 * dates.
	 * 
	 * @param archiveDates
	 *            the specified archive dates
	 * @throws JSONException
	 *             json exception
	 * @throws RepositoryException
	 *             repository exception
	 */
	private void removeForUnpublishedArticles(
			final List<JSONObject> archiveDates) throws JSONException,
			RepositoryException {
		final Iterator<JSONObject> iterator = archiveDates.iterator();
		while (iterator.hasNext()) {
			final JSONObject archiveDate = iterator.next();
			if (0 == archiveDate
					.getInt(ArchiveDate.ARCHIVE_DATE_PUBLISHED_ARTICLE_COUNT)) {
				iterator.remove();
			}
		}
	}

	/**
	 * Gets the {@link ArchiveDateGAERepository} singleton.
	 * 
	 * @return the singleton
	 */
	public static ArchiveDateGAERepository getInstance() {
		return SingletonHolder.SINGLETON;
	}

	/**
	 * Private default constructor.
	 */
	private ArchiveDateGAERepository() {
	}

	/**
	 * Singleton holder.
	 * 
	 */
	private static final class SingletonHolder {

		/**
		 * Singleton.
		 */
		private static final ArchiveDateGAERepository SINGLETON = new ArchiveDateGAERepository();

		/**
		 * Private default constructor.
		 */
		private SingletonHolder() {
		}
	}
}
